import type {
  ObjectDirective,
} from 'vue'

const DEFAULT_URL = ''

// 定义图片的状态
enum State {
  loading= 0, // 加载中
  loaded= 1, // 已完成
  error= 2, // 错误
}

type ImageManagerOptions = {
  el:HTMLElement
  src:string
  state?:State
  loading:string // 加载中显示的图片
  error:string // 加载错误时显示的图片
}

// 定义图片管理器
export class ImageManager {
  el: HTMLElement
  src: string
  state: State
  loading: string
  error: string
  constructor (options:ImageManagerOptions) {
    this.el = options.el
    this.src = options.src
    this.state = State.loading
    this.loading = options.loading
    this.error = options.error
  }

  update (src) {
    const currentSrc = this.src
    if (src !== currentSrc) {
      this.src = src
      this.state = State.loading
    }
  }

  // 给图片设置路径
  render (src) {
    this.el.setAttribute('src', src)
  }

  load (next) {
    if (this.state > State.loading) {
      return
    }
    this.renderSrc(next)
  }

  // render
  renderSrc (next) {
    loadImage(this.src).then(() => {
      this.state = State.loaded
      this.render(this.src)
      next && next()
    }).catch((e:Error) => {
      this.state = State.error
      this.render(this.error)
      console.warn(`load failed with src image(${this.src}) and the error msg is ${e.message}`)
      next && next()
    })
  }
}

function loadImage (src) {
  return new Promise((resolve, reject) => {
    const image = new Image()

    image.onload = function () {
      resolve(true)
      dispose()
    }

    image.onerror = function (e) {
      reject(e)
      dispose()
    }

    image.src = src

    function dispose () {
      image.onload = image.onerror = null
    }
  })
}

class Lazy {
  managerQueue: any[]
  loading: any
  error: any
  observer: any
  constructor (options) {
    this.managerQueue = []
    this.initIntersectionObserver()

    this.loading = options.loading || DEFAULT_URL
    this.error = options.error || DEFAULT_URL
  }

  add (el:HTMLElement, binding) {
    const src = binding.value

    const manager = new ImageManager({
      el,
      src,
      loading: this.loading,
      error: this.error,
    })

    this.managerQueue.push(manager)

    this.observer.observe(el)
  }

  removeManager (manager: ImageManager) {
    const index = this.managerQueue.indexOf(manager)
    if (index > -1) {
      this.managerQueue.splice(index, 1)
    }
    if (this.observer) {
      this.observer.unobserve(manager.el)
    }
  }

  initIntersectionObserver () {
    this.observer = new IntersectionObserver((entries) => {
      entries.forEach((entry) => {
        if (entry.isIntersecting) {
          const manager = this.managerQueue.find((manager) => {
            return manager.el === entry.target
          })
          if (manager) {
            if (manager.state === State.loaded) {
              this.removeManager(manager)
              return
            }
            manager.load()
          }
        }
      })
    }, {
      rootMargin: '0px',
      threshold: 0,
    })
  }
}

const lazyImg = function (options = {}) {
  const lazy = new Lazy(options)

  return {
    mounted: lazy.add.bind(lazy),
  } as ObjectDirective
}

export default lazyImg
